import 'package:dio/dio.dart';

/// 本文件集中封装了调用 GitCode API 所需的 HTTP 客户端、数据模型
/// 以及通用的解析逻辑，便于在 UI 层直接复用，避免重复拼装请求。

/// GitCode API 的轻量封装，负责统一请求参数、超时和错误处理。
class GitCodeApiClient {
  GitCodeApiClient({Dio? dio})
      : _dio = dio ??
            Dio(
              BaseOptions(
                // 默认基准端点指向 GitCode v5 API，所有请求在此路径下拼接。
                baseUrl: 'https://api.gitcode.com/api/v5',
                // 连接和读取阶段都限定为 5 秒，能在弱网场景下快速失败，提示用户重试。
                connectTimeout: const Duration(seconds: 5),
                receiveTimeout: const Duration(seconds: 5),
              ),
            ) {
    // 初始化时设置日志拦截器
    _setupLogging();
  }

  // 添加日志拦截器，帮助调试
  void _setupLogging() {
    _dio.interceptors.add(LogInterceptor(
      responseBody: true,
      requestBody: true,
      responseHeader: true,
      requestHeader: true,
    ));
  }

  final Dio _dio;

  /// 通过登录名获取用户详情，可选地携带 token 进行授权访问。
  Future<GitCodeUser> fetchUser(
    String username, {
    String? personalToken,
    bool allowSearchFallback = true,
  }) async {
    // 统一去除空白，避免因为用户误输入空格导致请求 404。
    final trimmed = username.trim();
    if (trimmed.isEmpty) {
      throw const GitCodeApiException('用户名不能为空');
    }

    try {
      final response = await _dio.get<Map<String, dynamic>>(
        '/users/${Uri.encodeComponent(trimmed)}',
        options: Options(
          headers: _buildHeaders(personalToken),
          responseType: ResponseType.json,
          // 允许 4xx 结果进入响应分支，以便输出更友好的提示。
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      final statusCode = response.statusCode ?? 0;
      switch (statusCode) {
        case 200:
          final data = response.data;
          if (data == null) {
            throw const GitCodeApiException('接口返回为空，请稍后重试');
          }
          return GitCodeUser.fromJson(data);
        case 401:
          throw const GitCodeApiException('未授权，请检查 access token 或权限');
        case 404:
          if (allowSearchFallback &&
              personalToken != null &&
              personalToken.isNotEmpty) {
            // 优先通过搜索接口尝试“昵称 -> 登录名”的映射，提升用户输入容错度。
            final fallbackLogin = await _searchLoginByKeyword(
              trimmed,
              personalToken: personalToken,
            );
            if (fallbackLogin != null && fallbackLogin != trimmed) {
              // 一旦拿到真实 login，关闭二次 fallback，以免造成递归。
              return fetchUser(
                fallbackLogin,
                personalToken: personalToken,
                allowSearchFallback: false,
              );
            }
          }
          throw GitCodeApiException(
            '未找到用户 $trimmed，若输入的是展示昵称，请改用登录名或携带 token 搜索。',
          );
        default:
          throw GitCodeApiException(
            '查询失败 (HTTP $statusCode)，请稍后重试',
          );
      }
    } on DioException catch (error) {
      if (error.type == DioExceptionType.connectionTimeout ||
          error.type == DioExceptionType.receiveTimeout) {
        throw const GitCodeApiException('请求超时，请检查网络后重试');
      }
      if (error.type == DioExceptionType.badResponse) {
        // 服务器已响应，但状态码 >= 400，由于 validateStatus 只放行 < 500，此处意味着 4xx。
        throw GitCodeApiException(
          '请求异常：HTTP ${error.response?.statusCode ?? '-'}',
        );
      }
      // 其它类型（取消、未知等）直接透传人类可读信息。
      throw GitCodeApiException(error.message ?? '未知网络错误');
    }
  }

  /// 构造通用请求头，必要时附加 Bearer token。
  Map<String, String> _buildHeaders(String? personalToken) {
    return {
      if (personalToken != null && personalToken.isNotEmpty)
        'Authorization': 'Bearer $personalToken',
    };
  }

  /// 当用户输入昵称时，通过搜索接口尝试获取真实 login。
  Future<String?> _searchLoginByKeyword(
    String keyword, {
    required String personalToken,
  }) async {
    try {
      final response = await _dio.get<List<dynamic>>(
        '/search/users',
        queryParameters: {
          'q': keyword,
          'per_page': 1,
          'access_token': personalToken,
        },
        options: Options(
          headers: _buildHeaders(personalToken),
          responseType: ResponseType.json,
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      if ((response.statusCode ?? 0) == 200) {
        final list = response.data;
        if (list == null || list.isEmpty) {
          return null;
        }
        final first = list.first;
        if (first is Map<String, dynamic>) {
          // 只取第一个候选项的 login，命中率通常最高。
          return first['login'] as String?;
        }
      } else if ((response.statusCode ?? 0) == 401) {
        // Token 不合法时立即抛出，提示用户调整配置。
        throw const GitCodeApiException('搜索需要有效的 access token');
      }
    } on DioException {
      return null;
    }
    return null;
  }

  /// 调用 `/search/users`，返回符合关键字的用户简要信息。
  Future<List<GitCodeSearchUser>> searchUsers({
    required String keyword,
    required String personalToken,
    int perPage = 10,
    int page = 1,
  }) async {
    // 用户搜索同样需要去除前后空格，避免无效查询。
    final trimmed = keyword.trim();
    if (trimmed.isEmpty) {
      throw const GitCodeApiException('请输入搜索关键字');
    }

    try {
      // 先获取动态类型响应
      final response = await _dio.get<dynamic>(
        '/search/users',
        queryParameters: <String, dynamic>{
          'q': trimmed,
          'access_token': personalToken,
          // clamp 可以阻止业务层传入不合法的分页参数，避免后端报错。
          'per_page': perPage.clamp(1, 50),
          'page': page.clamp(1, 100),
        },
        options: Options(
          headers: _buildHeaders(personalToken),
          responseType: ResponseType.json,
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      final statusCode = response.statusCode ?? 0;
      if (statusCode == 401) {
        throw const GitCodeApiException('Token 无效或权限不足，无法搜索用户');
      }
      if (statusCode != 200) {
        throw GitCodeApiException('搜索用户失败 (HTTP $statusCode)');
      }

      // 处理空响应
      if (response.data == null) {
        return [];
      }

      // 处理不同的响应类型
      final data = response.data;
      List<dynamic> usersList = [];

      if (data is List<dynamic>) {
        // 正常情况：直接返回列表
        usersList = data;
      } else if (data is Map<String, dynamic>) {
        // 尝试多种可能的字段名
        final possibleFields = ['items', 'users', 'data', 'results'];

        for (var field in possibleFields) {
          if (data.containsKey(field)) {
            final fieldData = data[field];
            if (fieldData is List<dynamic>) {
              usersList = fieldData;
              break;
            }
          }
        }

        // 如果没有找到有效的列表字段，返回空列表
        if (usersList.isEmpty) {
          return [];
        }
      }

      // 安全地转换数据
      try {
        return usersList
            .where((item) => item is Map<String, dynamic>)
            .map((item) =>
                GitCodeSearchUser.fromJson(item as Map<String, dynamic>))
            .toList();
      } catch (e) {
        // 数据解析错误，返回空列表
        return [];
      }
    } on DioException catch (error) {
      // 更详细的错误处理
      if (error.response != null) {
        throw GitCodeApiException(
            '搜索用户失败 (HTTP ${error.response?.statusCode}): ${error.response?.data}');
      } else if (error.type == DioExceptionType.connectionTimeout ||
          error.type == DioExceptionType.receiveTimeout) {
        throw const GitCodeApiException('网络超时，请检查网络连接后重试');
      } else {
        throw GitCodeApiException('搜索用户失败: ${error.message}');
      }
    } catch (e) {
      // 捕获所有其他异常
      throw GitCodeApiException('搜索用户时发生未知错误: ${e.toString()}');
    }
  }

  /// 调用 `/search/repositories`，支持语言、排序等可选参数。
  Future<List<GitCodeRepository>> searchRepositories({
    required String keyword,
    required String personalToken,
    String? language,
    String? sort,
    String? order,
    int perPage = 10,
    int page = 1,
  }) async {
    final trimmed = keyword.trim();
    if (trimmed.isEmpty) {
      throw const GitCodeApiException('请输入搜索关键字');
    }

    final queryParameters = <String, dynamic>{
      'q': trimmed,
      'access_token': personalToken,
      'per_page': perPage.clamp(1, 50),
      'page': page.clamp(1, 100),
      // 以下三个参数均为可选过滤项，后端若为空会忽略。
      if (language != null && language.isNotEmpty) 'language': language,
      if (sort != null && sort.isNotEmpty) 'sort': sort,
      if (order != null && order.isNotEmpty) 'order': order,
    };

    try {
      // 先获取动态类型响应
      final response = await _dio.get<dynamic>(
        '/search/repositories',
        queryParameters: queryParameters,
        options: Options(
          headers: _buildHeaders(personalToken),
          responseType: ResponseType.json,
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      final statusCode = response.statusCode ?? 0;
      if (statusCode == 401) {
        throw const GitCodeApiException('Token 无效或权限不足，无法搜索仓库');
      }
      if (statusCode != 200) {
        throw GitCodeApiException('搜索仓库失败 (HTTP $statusCode)');
      }

      // 处理空响应
      if (response.data == null) {
        return [];
      }

      // 处理不同的响应类型
      final data = response.data;
      List<dynamic> repositoriesList = [];

      if (data is List<dynamic>) {
        // 正常情况：直接返回列表
        repositoriesList = data;
      } else if (data is Map<String, dynamic>) {
        // 尝试多种可能的字段名
        final possibleFields = [
          'items',
          'repositories',
          'repos',
          'data',
          'results'
        ];

        for (var field in possibleFields) {
          if (data.containsKey(field)) {
            final fieldData = data[field];
            if (fieldData is List<dynamic>) {
              repositoriesList = fieldData;
              break;
            }
          }
        }

        // 如果没有找到有效的列表字段，返回空列表
        if (repositoriesList.isEmpty) {
          return [];
        }
      }

      // 安全地转换数据
      try {
        return repositoriesList
            .where((item) => item is Map<String, dynamic>)
            .map((item) =>
                GitCodeRepository.fromJson(item as Map<String, dynamic>))
            .toList();
      } catch (e) {
        // 数据解析错误，返回空列表
        return [];
      }
    } on DioException catch (error) {
      // 更详细的错误处理
      if (error.response != null) {
        throw GitCodeApiException(
            '搜索仓库失败 (HTTP ${error.response?.statusCode}): ${error.response?.data}');
      } else if (error.type == DioExceptionType.connectionTimeout ||
          error.type == DioExceptionType.receiveTimeout) {
        throw const GitCodeApiException('网络超时，请检查网络连接后重试');
      } else {
        throw GitCodeApiException('搜索仓库失败: ${error.message}');
      }
    } catch (e) {
      // 捕获所有其他异常
      throw GitCodeApiException('搜索仓库时发生未知错误: ${e.toString()}');
    }
  }

  /// 获取用户的组织列表
  Future<List<GitCodeOrganization>> getOrganizations({
    required String username,
    required String personalToken,
    int perPage = 10,
    int page = 1,
  }) async {
    final trimmed = username.trim();
    if (trimmed.isEmpty) {
      throw const GitCodeApiException('用户名不能为空');
    }

    try {
      // 先获取动态类型响应
      final response = await _dio.get<dynamic>(
        '/users/${Uri.encodeComponent(trimmed)}/orgs',
        queryParameters: <String, dynamic>{
          'access_token': personalToken,
          'per_page': perPage.clamp(1, 50),
          'page': page.clamp(1, 100),
        },
        options: Options(
          headers: _buildHeaders(personalToken),
          responseType: ResponseType.json,
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      final statusCode = response.statusCode ?? 0;
      if (statusCode == 401) {
        throw const GitCodeApiException('Token 无效或权限不足，无法获取组织');
      }
      if (statusCode != 200) {
        // 针对400错误提供更友好的提示
        if (statusCode == 400) {
          throw GitCodeApiException('获取组织列表失败 (参数错误)，请检查请求参数是否正确');
        }
        throw GitCodeApiException('获取组织列表失败 (HTTP $statusCode)');
      }

      // 处理空响应
      if (response.data == null) {
        return [];
      }

      // 处理不同的响应类型
      final data = response.data;
      List<dynamic> organizationsList = [];

      if (data is List<dynamic>) {
        // 正常情况：直接返回列表
        organizationsList = data;
      } else if (data is Map<String, dynamic>) {
        // 尝试多种可能的字段名
        final possibleFields = [
          'items',
          'organizations',
          'orgs',
          'data',
          'results'
        ];

        for (var field in possibleFields) {
          if (data.containsKey(field)) {
            final fieldData = data[field];
            if (fieldData is List<dynamic>) {
              organizationsList = fieldData;
              break;
            }
          }
        }

        // 如果没有找到有效的列表字段，返回空列表
        if (organizationsList.isEmpty) {
          return [];
        }
      }

      // 安全地转换数据
      try {
        return organizationsList
            .where((item) => item is Map<String, dynamic>)
            .map((item) =>
                GitCodeOrganization.fromJson(item as Map<String, dynamic>))
            .toList();
      } catch (e) {
        // 数据解析错误，返回空列表
        return [];
      }
    } on DioException catch (error) {
      // 更详细的错误处理
      if (error.response != null) {
        throw GitCodeApiException(
            '获取组织列表失败 (HTTP ${error.response?.statusCode}): ${error.response?.data}');
      } else if (error.type == DioExceptionType.connectionTimeout ||
          error.type == DioExceptionType.receiveTimeout) {
        throw const GitCodeApiException('网络超时，请检查网络连接后重试');
      } else {
        throw GitCodeApiException('获取组织列表失败: ${error.message}');
      }
    } catch (e) {
      // 捕获所有其他异常
      throw GitCodeApiException('获取组织列表时发生未知错误: ${e.toString()}');
    }
  }

  /// 获取用户的仓库列表
  Future<List<GitCodeRepository>> getUserRepositories({
    required String username,
    required String personalToken,
    String? sort,
    String? direction,
    int perPage = 10,
    int page = 1,
  }) async {
    final trimmed = username.trim();
    if (trimmed.isEmpty) {
      throw const GitCodeApiException('用户名不能为空');
    }

    final queryParameters = <String, dynamic>{
      'access_token': personalToken,
      'per_page': perPage.clamp(1, 50),
      'page': page.clamp(1, 100),
      if (sort != null && sort.isNotEmpty) 'sort': sort,
      if (direction != null && direction.isNotEmpty) 'direction': direction,
    };

    try {
      // 先获取动态类型响应
      final response = await _dio.get<dynamic>(
        '/users/${Uri.encodeComponent(trimmed)}/repos',
        queryParameters: queryParameters,
        options: Options(
          headers: _buildHeaders(personalToken),
          responseType: ResponseType.json,
          validateStatus: (status) => status != null && status < 500,
        ),
      );

      final statusCode = response.statusCode ?? 0;
      if (statusCode == 401) {
        throw const GitCodeApiException('Token 无效或权限不足，无法获取仓库');
      }
      if (statusCode != 200) {
        throw GitCodeApiException('获取仓库列表失败 (HTTP $statusCode)');
      }

      // 处理空响应
      if (response.data == null) {
        return [];
      }

      // 处理不同的响应类型
      final data = response.data;
      List<dynamic> repositoriesList = [];

      if (data is List<dynamic>) {
        // 正常情况：直接返回列表
        repositoriesList = data;
      } else if (data is Map<String, dynamic>) {
        // 尝试多种可能的字段名
        final possibleFields = [
          'items',
          'repositories',
          'repos',
          'data',
          'results'
        ];

        for (var field in possibleFields) {
          if (data.containsKey(field)) {
            final fieldData = data[field];
            if (fieldData is List<dynamic>) {
              repositoriesList = fieldData;
              break;
            }
          }
        }

        // 如果没有找到有效的列表字段，尝试将整个Map作为单个仓库处理
        if (repositoriesList.isEmpty) {
          // 检查是否包含仓库特征字段
          if (data.containsKey('name') ||
              data.containsKey('full_name') ||
              data.containsKey('fullName')) {
            repositoriesList = [data];
          } else {
            // 无法识别的响应格式，返回空列表而不是抛出异常
            return [];
          }
        }
      }

      // 安全地转换数据
      try {
        return repositoriesList
            .where((item) => item is Map<String, dynamic>)
            .map((item) =>
                GitCodeRepository.fromJson(item as Map<String, dynamic>))
            .toList();
      } catch (e) {
        // 数据解析错误，返回空列表
        return [];
      }
    } on DioException catch (error) {
      // 更详细的错误处理
      if (error.response != null) {
        throw GitCodeApiException(
            '获取仓库列表失败 (HTTP ${error.response?.statusCode}): ${error.response?.data}');
      } else if (error.type == DioExceptionType.connectionTimeout ||
          error.type == DioExceptionType.receiveTimeout) {
        throw const GitCodeApiException('网络超时，请检查网络连接后重试');
      } else {
        throw GitCodeApiException('获取仓库列表失败: ${error.message}');
      }
    } catch (e) {
      // 捕获所有其他异常
      throw GitCodeApiException('获取仓库列表时发生未知错误: ${e.toString()}');
    }
  }
}

class GitCodeUser {
  GitCodeUser({
    required this.login,
    required this.avatarUrl,
    this.name,
    this.bio,
    this.htmlUrl,
    this.publicRepos,
    this.followers,
    this.following,
    this.createdAt,
  });

  factory GitCodeUser.fromJson(Map<String, dynamic> json) {
    return GitCodeUser(
      login: json['login'] as String? ?? '',
      avatarUrl: json['avatar_url'] as String? ?? '',
      name: json['name'] as String?,
      bio: json['bio'] as String?,
      htmlUrl: json['html_url'] as String?,
      publicRepos: _safeInt(json['public_repos']),
      followers: _safeInt(json['followers']),
      following: _safeInt(json['following']),
      createdAt: json['created_at'] as String?,
    );
  }

  /// GitCode 唯一登录名，可用于后续接口请求。
  final String login;

  /// 头像的完整 URL，用于展示用户头像。
  final String avatarUrl;

  /// 用户在个人资料中填写的展示昵称，可能为空。
  final String? name;

  /// 个人简介或签名信息。
  final String? bio;

  /// 用户主页链接，通常指向 GitCode Web。
  final String? htmlUrl;

  /// 用户公开仓库的数量。
  final int? publicRepos;

  /// 粉丝（followers）数量。
  final int? followers;

  /// 关注（following）数量。
  final int? following;

  /// 账号创建时间，ISO 时间字符串。
  final String? createdAt;
}

/// 将接口返回的动态数值转换为 int，兼容字符串与数字。
int? _safeInt(dynamic value) {
  if (value == null) {
    return null;
  }
  if (value is int) {
    return value;
  }
  if (value is String) {
    // 有些字段可能以字符串形式返回整型，此处尝试解析。
    return int.tryParse(value);
  }
  return null;
}

class GitCodeApiException implements Exception {
  const GitCodeApiException(this.message);

  final String message;

  @override
  String toString() => 'GitCodeApiException: $message';
}

class GitCodeSearchUser {
  GitCodeSearchUser({
    required this.login,
    required this.avatarUrl,
    this.name,
    this.htmlUrl,
    this.createdAt,
  });

  factory GitCodeSearchUser.fromJson(Map<String, dynamic> json) {
    return GitCodeSearchUser(
      login: json['login'] as String? ?? '',
      avatarUrl: json['avatar_url'] as String? ?? '',
      name: json['name'] as String?,
      htmlUrl: json['html_url'] as String?,
      createdAt: json['created_at'] as String?,
    );
  }

  /// 搜索结果中的登录名。
  final String login;

  /// 搜索结果中的头像地址。
  final String avatarUrl;

  /// 搜索结果中的展示名称。
  final String? name;

  /// 用户主页链接。
  final String? htmlUrl;

  /// 用户创建时间。
  final String? createdAt;
}

class GitCodeOrganization {
  GitCodeOrganization({
    required this.login,
    required this.avatarUrl,
    this.name,
    this.description,
    this.htmlUrl,
    this.membersCount,
  });

  factory GitCodeOrganization.fromJson(Map<String, dynamic> json) {
    return GitCodeOrganization(
      login: json['login'] as String? ?? '',
      avatarUrl: json['avatar_url'] as String? ?? '',
      name: json['name'] as String?,
      description: json['description'] as String?,
      htmlUrl: json['html_url'] as String?,
      membersCount: _safeInt(json['members_count']),
    );
  }

  /// 组织的唯一登录名
  final String login;

  /// 组织头像的URL
  final String avatarUrl;

  /// 组织显示名称
  final String? name;

  /// 组织描述
  final String? description;

  /// 组织主页URL
  final String? htmlUrl;

  /// 成员数量
  final int? membersCount;
}

class GitCodeRepository {
  GitCodeRepository({
    required this.fullName,
    required this.webUrl,
    this.description,
    this.language,
    this.updatedAt,
    this.stars,
    this.forks,
    this.watchers,
    this.ownerLogin,
    this.isPrivate,
  });

  factory GitCodeRepository.fromJson(Map<String, dynamic> json) {
    final owner = json['owner'];
    return GitCodeRepository(
      fullName: json['full_name'] as String? ?? '',
      webUrl: json['web_url'] as String? ?? '',
      description: json['description'] as String?,
      language: json['language'] as String?,
      updatedAt: json['updated_at'] as String?,
      stars:
          _safeInt(json['stargazers_count']) ?? _safeInt(json['stars_count']),
      forks: _safeInt(json['forks_count']),
      watchers: _safeInt(json['watchers_count']),
      ownerLogin: owner is Map<String, dynamic>
          // 兼容不同字段命名（name/path），以防接口字段发生差异。
          ? owner['name'] as String? ?? owner['path'] as String?
          : null,
      isPrivate: _safeBool(json['private']),
    );
  }

  /// 仓库的全名，形如 "owner/repo"。
  final String fullName;

  /// 仓库在 GitCode 上的访问 URL。
  final String webUrl;

  /// 仓库描述，可能为空。
  final String? description;

  /// 主要编程语言。
  final String? language;

  /// 最近一次更新时间。
  final String? updatedAt;

  /// Stargazers 数量，优先读取 `stargazers_count`。
  final int? stars;

  /// Fork 数量。
  final int? forks;

  /// Watchers 数量。
  final int? watchers;

  /// 仓库所有者的登录名或路径。
  final String? ownerLogin;

  /// 是否私有仓库。
  final bool? isPrivate;
}

/// GitCode 接口可能返回 0/1、'true'/'false'，统一转成 bool。
bool? _safeBool(dynamic value) {
  if (value == null) {
    return null;
  }
  if (value is bool) {
    return value;
  }
  if (value is int) {
    // GitCode 某些布尔字段会用 0/1 表示。
    return value != 0;
  }
  if (value is String) {
    if (value == '1' || value.toLowerCase() == 'true') {
      return true;
    }
    if (value == '0' || value.toLowerCase() == 'false') {
      return false;
    }
  }
  return null;
}
